﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.Geodatabase;
using System.Diagnostics;

namespace CanSWAT.ArcSWAT.Soil
{
    /// <summary>
    /// Soil derivs
    /// </summary>
    class SoilDeriv
    {
        public SoilDeriv(IRow r)
        {
            _id = ArcGISRow.ReadString(r, "SOIL_KEY_A");
            _depth_up = ArcGISRow.ReadInteger(r, "UDEPTH") * 10; //cm->mm
            _depth_bottom = ArcGISRow.ReadInteger(r, "LDEPTH") * 10;//cm->mm
            getThick();

            _BulkDensity = ArcGISRow.ReadDouble(r, "BD");//g/cm3 == Mg/m3
            _AvailableWaterCapacity = ArcGISRow.ReadDouble(r, "KP33") - ArcGISRow.ReadDouble(r, "KP1500");
            if (_AvailableWaterCapacity <= 0)
            {
                System.Diagnostics.Debug.WriteLine("Wrong available water capacity!");
                _AvailableWaterCapacity = 0.0;
            }
            _SaturatedHydraulicConductivity = ArcGISRow.ReadDouble(r, "KSAT") * 10.0;//cm/h -> mm/hr
            _OrganicCarbon = ArcGISRow.ReadDouble(r, "ORGCARB");
            _Clay = ArcGISRow.ReadDouble(r, "TCLAY");
            _Silt = ArcGISRow.ReadDouble(r, "TSILT");
            _Sand = ArcGISRow.ReadDouble(r, "TSAND");
            _USLEK = ArcGISRow.ReadDouble(r, "K");
            _ElectricalConductivity = ArcGISRow.ReadDouble(r, "EC");
            CheckClaySiltSand();
        }

        public SoilDeriv(int depth_up, int depth_bottom)
        {
            _depth_bottom = depth_bottom;
            _depth_up = depth_up;
            getThick();
        }

        private void getThick()
        {
            _thick = _depth_bottom - _depth_up;
            _thick = Math.Max(0, _thick);
        }

        private string _id = "";
        private int _depth_up = 0;
        private int _depth_bottom = 0;
        private int _thick = 0;

        private double _BulkDensity = 0.0;//Mg/m3
        private double _AvailableWaterCapacity = 0.0;//mm H2O/mm soil
        private double _SaturatedHydraulicConductivity = 0.0;//mm/hr
        private double _OrganicCarbon = 0.0; //%
        private double _Clay = 100.0;//% default is all clay
        private double _Silt = 0.0;//%
        private double _Sand = 0.0;//%
        private double _Rock = 0.0;//%, don't  read from GeoDatabase, just set as 0
        private double _Albedo = 0.01; //can't find in the GeoDatabase
        private double _USLEK = 0.0;
        private double _ElectricalConductivity = 0.0;
        private double _CalciumCarbonate = 0.0;//%, Soil CaCo3, currently is not used, only in SWAT2012
        private double _PH = 0.0;//currently is not used, only in SWAT2012

        public int Depth { get { return _depth_up; } set { _depth_up = value; getThick(); } }
        public int DepthBottom { get { return _depth_bottom; } set { _depth_bottom = value; getThick(); } }
        public int Thick { get { return _thick; } }
        public string ID { get { return _id; } }

        public double BulkDensity { get { return _BulkDensity; } set { _BulkDensity = value; } }
        public double AvailableWaterCapacity { get { return _AvailableWaterCapacity; } set { _AvailableWaterCapacity = value; } }
        public double SaturatedHydraulicConductivity { get { return _SaturatedHydraulicConductivity; } set { _SaturatedHydraulicConductivity = value; } }
        public double OrganicCarbon { get { return _OrganicCarbon; } set { _OrganicCarbon = value; } }
        public double Clay { get { return _Clay; } set { _Clay = value; CheckClaySiltSand(); } }
        public double Silt { get { return _Silt; } set { _Silt = value; CheckClaySiltSand(); } }
        public double Sand { get { return _Sand; } set { _Sand = value; CheckClaySiltSand(); } }
        public double Rock { get { return _Rock; } set { _Rock = value; } }
        public double Albedo { get { return _Albedo; } set { _Albedo = value; } }
        public double USLEK { get { return _USLEK; } set { _USLEK = value; } }
        public double ElectricalConductivity { get { return _ElectricalConductivity; } set { _ElectricalConductivity = value; } }
        public double CalciumCarbonate { get { return _CalciumCarbonate; } set { _CalciumCarbonate = value; } }
        public double PH { get { return _PH; } set { _PH = value; } }

        /// <summary>
        /// Make sure the total of clay, silt and sand is 100%
        /// </summary>
        private void CheckClaySiltSand()
        {
            if (_Clay + _Silt + _Sand != 100)
            {
                _Sand = 100 - _Clay - _Silt;
                Debug.WriteLine("Correct clay, silt and sand percentage for " + ID);
            }
        }

        public SoilDeriv Clone()
        {
            return this.MemberwiseClone() as SoilDeriv;
        }

        public SoilDeriv Clone(int depth_up, int depth_bottom)
        {
            if (Depth >= depth_bottom || DepthBottom <= depth_up) return null;

            SoilDeriv deriv = Clone();
            deriv.Depth = Math.Max(depth_up, Depth);
            deriv.DepthBottom = Math.Min(depth_bottom, DepthBottom);
            return deriv;
        }

        /// <summary>
        /// if overlay with given deriv
        /// </summary>
        /// <param name="deriv"></param>
        /// <returns></returns>
        public bool isOverlay(SoilDeriv deriv)
        {
            if (Depth <= deriv.DepthBottom ||
                DepthBottom >= deriv.Depth) return false;

            return true;
        }

        /// <summary>
        /// if connected with given deriv
        /// </summary>
        /// <param name="deriv"></param>
        /// <returns></returns>
        public bool isConnected(SoilDeriv deriv)
        {
            if (deriv == null) return false;

            return this.Depth == deriv.DepthBottom || this.DepthBottom == deriv.Depth;
        }

        public bool isSameDepth(SoilDeriv deriv)
        {
            if (deriv == null) return false;

            return Depth == deriv.Depth && DepthBottom == deriv.DepthBottom;
        }

        /// <summary>
        /// Connect two deriv to one. The properties would be calculated based on thick
        /// </summary>
        /// <param name="deriv"></param>
        /// <returns></returns>
        public SoilDeriv Connect(SoilDeriv deriv)
        {
            if (!isConnected(deriv))  //only connected derivs could be connected
                throw new Exception("Derivs are not connected.");

            SoilDeriv connectDeriv = new SoilDeriv(
                Math.Min(this.Depth, deriv.Depth),
                Math.Max(this.DepthBottom, deriv.DepthBottom));

            //calculate height average of all properties
            double ratio_this = Convert.ToDouble(Thick) / Convert.ToDouble(connectDeriv.Thick);
            double ratio_another = 1.0 - ratio_this;

            //todo -- add property calculation here
            CalculateProperties(connectDeriv,
                this, ratio_this,
                deriv, ratio_another);

            return connectDeriv;
        }

        /// <summary>
        /// Merge two deriv to one. The properties would be calculated based percentage
        /// </summary>
        /// <param name="deriv"></param>
        /// <returns></returns>
        public SoilDeriv Merge(double percentage, SoilDeriv deriv_other, double percentage_other)
        {
            if (!isSameDepth(deriv_other))  //only same derivs could be merged
                throw new Exception("Derivs are not same size.");

            SoilDeriv mergeDeriv = this.Clone();

            //todo -- add property calculation here based on percentage
            CalculateProperties(mergeDeriv,
                this, percentage,
                deriv_other, percentage_other);

            return mergeDeriv;
        }

        /// <summary>
        /// Calculate new properties based on two other derivs and ratio
        /// </summary>
        /// <param name="newDeriv"></param>
        /// <param name="deriv"></param>
        /// <param name="percentage"></param>
        /// <param name="deriv_other"></param>
        /// <param name="percentage_other"></param>
        private static void CalculateProperties(SoilDeriv newDeriv, SoilDeriv deriv, double percentage, SoilDeriv deriv_other, double percentage_other)
        {
            newDeriv.BulkDensity = deriv.BulkDensity * percentage + deriv_other.BulkDensity * percentage_other;
            newDeriv.AvailableWaterCapacity = deriv.AvailableWaterCapacity * percentage + deriv_other.AvailableWaterCapacity * percentage_other;
            newDeriv.SaturatedHydraulicConductivity = deriv.SaturatedHydraulicConductivity * percentage + deriv_other.SaturatedHydraulicConductivity * percentage_other;
            newDeriv.OrganicCarbon = deriv.OrganicCarbon * percentage + deriv_other.OrganicCarbon * percentage_other;
            newDeriv.Clay = deriv.Clay * percentage + deriv_other.Clay * percentage_other;
            newDeriv.Silt = deriv.Silt * percentage + deriv_other.Silt * percentage_other;
            newDeriv.Sand = deriv.Sand * percentage + deriv_other.Sand * percentage_other;
            newDeriv.Rock = deriv.Rock * percentage + deriv_other.Rock * percentage_other;
            newDeriv.Albedo = deriv.Albedo * percentage + deriv_other.Albedo * percentage_other;
            newDeriv.USLEK = deriv.USLEK * percentage + deriv_other.USLEK * percentage_other;
            newDeriv.ElectricalConductivity = deriv.ElectricalConductivity * percentage + deriv_other.ElectricalConductivity * percentage_other;
            newDeriv.CalciumCarbonate = deriv.CalciumCarbonate * percentage + deriv_other.CalciumCarbonate * percentage_other;
            newDeriv.PH = deriv.PH * percentage + deriv_other.PH * percentage_other;
        }

        public string ToUserSoilTableString()
        {
            return string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}",
                DepthBottom,
                BulkDensity,
                AvailableWaterCapacity,
                SaturatedHydraulicConductivity,
                OrganicCarbon,
                Clay,
                Silt,
                Sand,
                Rock,
                Albedo,
                USLEK,
                ElectricalConductivity);
        }
    }
}
